package com.happy.unitconvert.service.impl;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.happy.unitconvert.config.properties.AppProperties;
import com.happy.unitconvert.model.dto.ExchangeRateResponse;
import com.happy.unitconvert.service.ExchangeRateCacheService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

/**
 * 汇率缓存服务实现
 * 基于Redis的汇率数据缓存管理
 *
 * @author Happy
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class ExchangeRateCacheServiceImpl implements ExchangeRateCacheService {

  private final ReactiveRedisTemplate<String, String> reactiveRedisTemplate;
  private final AppProperties appProperties;
  private final ObjectMapper objectMapper;

  // 缓存键前缀
  private static final String RATES_KEY_PREFIX = "exchange_rates:";
  private static final String RESPONSE_KEY_PREFIX = "exchange_response:";

  @Override
  public Mono<Void> cacheExchangeRates(String baseCurrency, Map<String, BigDecimal> rates) {
    String key = buildRatesKey(baseCurrency);
    log.debug("缓存汇率数据，基础货币: {}，键: {}", baseCurrency, key);

    return Mono.fromCallable(() -> objectMapper.writeValueAsString(rates))
        .flatMap(ratesJson -> reactiveRedisTemplate.opsForValue()
            .set(key, ratesJson, getCacheDuration()))
        .doOnSuccess(success -> {
          if (Boolean.TRUE.equals(success)) {
            log.info("成功缓存汇率数据，基础货币: {}，包含{}种货币", baseCurrency, rates.size());
          } else {
            log.warn("缓存汇率数据失败，基础货币: {}", baseCurrency);
          }
        })
        .doOnError(error -> log.error("缓存汇率数据异常，基础货币: {}，错误: {}",
            baseCurrency, error.getMessage()))
        .then()
        .onErrorResume(error -> {
          log.error("缓存操作失败，继续执行: {}", error.getMessage());
          return Mono.empty();
        });
  }

  @Override
  public Mono<Map<String, BigDecimal>> getCachedExchangeRates(String baseCurrency) {
    String key = buildRatesKey(baseCurrency);
    log.debug("获取缓存汇率数据，基础货币: {}，键: {}", baseCurrency, key);

    return reactiveRedisTemplate.opsForValue()
        .get(key)
        .flatMap(ratesJson -> {
          try {
            TypeReference<Map<String, BigDecimal>> typeRef = new TypeReference<Map<String, BigDecimal>>() {
            };
            Map<String, BigDecimal> rates = objectMapper.readValue(ratesJson, typeRef);
            log.debug("成功获取缓存汇率数据，基础货币: {}，包含{}种货币", baseCurrency, rates.size());
            return Mono.just(rates);
          } catch (Exception e) {
            log.error("解析缓存汇率数据失败，基础货币: {}，错误: {}", baseCurrency, e.getMessage());
            return Mono.empty();
          }
        })
        .switchIfEmpty(Mono.fromCallable(() -> {
          log.debug("未找到缓存汇率数据，基础货币: {}", baseCurrency);
          return new HashMap<>();
        }).then(Mono.empty()))
        .onErrorResume(error -> {
          log.error("获取缓存汇率数据异常，基础货币: {}，错误: {}", baseCurrency, error.getMessage());
          return Mono.empty();
        });
  }

  @Override
  public Mono<BigDecimal> getCachedExchangeRate(String fromCurrency, String toCurrency) {
    log.debug("获取特定货币对汇率，从{}到{}", fromCurrency, toCurrency);

    if (fromCurrency.equalsIgnoreCase(toCurrency)) {
      return Mono.just(BigDecimal.ONE);
    }

    return getCachedExchangeRates(fromCurrency)
        .map(rates -> rates.get(toCurrency.toUpperCase()))
        .filter(rate -> rate != null)
        .switchIfEmpty(
            // 如果没有找到直接汇率，尝试通过USD进行转换
            getCachedExchangeRates("USD")
                .flatMap(usdRates -> {
                  BigDecimal fromRate = usdRates.get(fromCurrency.toUpperCase());
                  BigDecimal toRate = usdRates.get(toCurrency.toUpperCase());

                  if (fromRate != null && toRate != null && fromRate.compareTo(BigDecimal.ZERO) != 0) {
                    BigDecimal rate = toRate.divide(fromRate, 10, RoundingMode.HALF_UP);
                    log.debug("通过USD计算汇率，{}到{}，汇率: {}", fromCurrency, toCurrency, rate);
                    return Mono.just(rate);
                  }
                  return Mono.empty();
                }));
  }

  @Override
  public Mono<Boolean> isCacheValid(String baseCurrency) {
    String key = buildRatesKey(baseCurrency);

    return reactiveRedisTemplate.hasKey(key)
        .doOnNext(exists -> log.debug("检查缓存是否存在，基础货币: {}，存在: {}", baseCurrency, exists))
        .onErrorReturn(false);
  }

  @Override
  public Mono<Void> clearCache(String baseCurrency) {
    if (baseCurrency == null) {
      log.info("清除所有汇率缓存");
      return reactiveRedisTemplate.scan()
          .filter(key -> key.startsWith(RATES_KEY_PREFIX) || key.startsWith(RESPONSE_KEY_PREFIX))
          .collectList()
          .flatMap(keys -> {
            if (keys.isEmpty()) {
              return Mono.just(0L);
            }
            return reactiveRedisTemplate.delete(keys.toArray(new String[0]));
          })
          .doOnSuccess(count -> log.info("已清除{}个汇率缓存项", count))
          .then();
    } else {
      log.info("清除指定货币缓存，基础货币: {}", baseCurrency);
      String ratesKey = buildRatesKey(baseCurrency);
      String responseKey = buildResponseKey(baseCurrency);

      return reactiveRedisTemplate.delete(ratesKey, responseKey)
          .doOnSuccess(count -> log.info("已清除{}个缓存项，基础货币: {}", count, baseCurrency))
          .then();
    }
  }

  @Override
  public Mono<Void> cacheExchangeRateResponse(String baseCurrency, ExchangeRateResponse response) {
    String key = buildResponseKey(baseCurrency);
    log.debug("缓存汇率响应数据，基础货币: {}，键: {}", baseCurrency, key);

    return Mono.fromCallable(() -> objectMapper.writeValueAsString(response))
        .flatMap(responseJson -> reactiveRedisTemplate.opsForValue()
            .set(key, responseJson, getCacheDuration()))
        .doOnSuccess(success -> {
          if (Boolean.TRUE.equals(success)) {
            log.info("成功缓存汇率响应数据，基础货币: {}", baseCurrency);
          } else {
            log.warn("缓存汇率响应数据失败，基础货币: {}", baseCurrency);
          }
        })
        .doOnError(error -> log.error("缓存汇率响应数据异常，基础货币: {}，错误: {}",
            baseCurrency, error.getMessage()))
        .then()
        .onErrorResume(error -> {
          log.error("缓存响应操作失败，继续执行: {}", error.getMessage());
          return Mono.empty();
        });
  }

  @Override
  public Mono<ExchangeRateResponse> getCachedExchangeRateResponse(String baseCurrency) {
    String key = buildResponseKey(baseCurrency);
    log.debug("获取缓存汇率响应数据，基础货币: {}，键: {}", baseCurrency, key);

    return reactiveRedisTemplate.opsForValue()
        .get(key)
        .flatMap(responseJson -> {
          try {
            ExchangeRateResponse response = objectMapper.readValue(responseJson, ExchangeRateResponse.class);
            log.debug("成功获取缓存汇率响应数据，基础货币: {}", baseCurrency);
            return Mono.just(response);
          } catch (Exception e) {
            log.error("解析缓存汇率响应数据失败，基础货币: {}，错误: {}", baseCurrency, e.getMessage());
            return Mono.empty();
          }
        })
        .switchIfEmpty(Mono.fromCallable(() -> {
          log.debug("未找到缓存汇率响应数据，基础货币: {}", baseCurrency);
          return null;
        }).then(Mono.empty()))
        .onErrorResume(error -> {
          log.error("获取缓存汇率响应数据异常，基础货币: {}，错误: {}", baseCurrency, error.getMessage());
          return Mono.empty();
        });
  }

  @Override
  public Mono<Long> getCacheExpiration(String baseCurrency) {
    String key = buildRatesKey(baseCurrency);

    return reactiveRedisTemplate.getExpire(key)
        .doOnNext(ttl -> log.debug("获取缓存过期时间，基础货币: {}，TTL: {}秒", baseCurrency, ttl))
        .onErrorReturn(Duration.ofSeconds(-2))
        .map(duration -> duration.getSeconds());
  }

  @Override
  public Mono<Boolean> setCacheExpiration(String baseCurrency, long expireSeconds) {
    String key = buildRatesKey(baseCurrency);

    return reactiveRedisTemplate.expire(key, Duration.ofSeconds(expireSeconds))
        .doOnNext(success -> log.debug("设置缓存过期时间，基础货币: {}，过期时间: {}秒，结果: {}",
            baseCurrency, expireSeconds, success))
        .onErrorReturn(false);
  }

  /**
   * 构建汇率缓存键
   */
  private String buildRatesKey(String baseCurrency) {
    return RATES_KEY_PREFIX + baseCurrency.toUpperCase();
  }

  /**
   * 构建响应缓存键
   */
  private String buildResponseKey(String baseCurrency) {
    return RESPONSE_KEY_PREFIX + baseCurrency.toUpperCase();
  }

  /**
   * 获取缓存持续时间
   */
  private Duration getCacheDuration() {
    int cacheMinutes = appProperties.getExchangeRate().getCacheMinutes();
    return Duration.ofMinutes(cacheMinutes);
  }
}
